package com.garrigou.spaceship.camera;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.Input.Peripheral;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.garrigou.spaceship.actor.Ship2D;
import com.garrigou.spaceship.screens.AbstractScreen;
import com.garrigou.spaceship.utils.VectorUtils;

public class OrthoCamController extends Actor
{
  private final ParallaxCamera  camera;
  private final Vector3         curr  = new Vector3();
  private final Vector3         last  = new Vector3(-1, -1, -1);
  private final Vector3         delta = new Vector3();
  private final SpriteBatch     parentBatch;
  private final TextureRegion[] layers;
  private Ship2D                ship;

  public OrthoCamController(SpriteBatch batch)
  {
    this.camera = new ParallaxCamera(AbstractScreen.GAME_VIEWPORT_WIDTH,
                                     AbstractScreen.GAME_VIEWPORT_HEIGHT);;

    Texture texture = new Texture(Gdx.files.internal("data/layers.png"));
    this.layers = new TextureRegion[3];
    this.layers[0] = new TextureRegion(texture, 0, 0, 542, 363);
    this.layers[1] = new TextureRegion(texture, 0, 363, 1024, 149);
    this.layers[2] = new TextureRegion(texture, 547, 0, 224, 51);

    // set some basic attributes
    this.parentBatch = batch;
    this.position = new Vector2();
    this.velocity = new Vector2();
    this.acceleration = new Vector2();
  }

  public void setShip(Ship2D ship)
  {
    this.ship = ship;
  }

  @Override
  public void act(float delta)
  {
    super.act(delta);

    calculCameraPosition(delta);

    if (this.ship == null) moveCamera(0, 0, -1);
    else moveCamera((int) /* this.ship.getShipPosition() */position.x, (int) /*
                                                                              * this.ship.
                                                                              * getShipPosition()
                                                                              */position.y, -1);

    Gdx.gl.glClearColor(242 / 255.0f, 210 / 255.0f, 111 / 255.0f, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    // keep camera in foreground layer bounds
    // boolean updateCamera = false;
    // if (camera.position.x < -1024 + camera.viewportWidth / 2)
    // {
    // camera.position.x = -1024 + (int) (camera.viewportWidth / 2);
    // updateCamera = true;
    // }
    //
    // if (camera.position.x > 1024 - camera.viewportWidth / 2)
    // {
    // camera.position.x = 1024 - (int) (camera.viewportWidth / 2);
    // updateCamera = true;
    // }

    // if (camera.position.y < 0)
    // {
    // camera.position.y = 0;
    // updateCamera = true;
    // }
    // arbitrary height of scene
    // if (camera.position.y > 400 - camera.viewportHeight / 2)
    // {
    // camera.position.y = 400 - (int) (camera.viewportHeight / 2);
    // updateCamera = true;
    // }

    // background layer, no parallax, centered around origin
    parentBatch.setProjectionMatrix(camera.calculateParallaxMatrix(0, 0));
    parentBatch.disableBlending();
    parentBatch.begin();
    parentBatch.draw(layers[0], -(int) (layers[0].getRegionWidth() / 2),
                     -(int) (layers[0].getRegionHeight() / 2));
    parentBatch.end();
    parentBatch.enableBlending();

    // midground layer, 0.5 parallax (move at half speed on x, full speed on y)
    // layer is 1024x320
    parentBatch.setProjectionMatrix(camera.calculateParallaxMatrix(0.5f, 1));
    parentBatch.begin();
    parentBatch.draw(layers[1], -512, -160);
    parentBatch.end();

    // foreground layer, 1.0 parallax (move at full speed)
    // layer is 2048x320
    parentBatch.setProjectionMatrix(camera.calculateParallaxMatrix(1f, 1));
    parentBatch.begin();
    for (int i = 0; i < 9; i++)
    {
      parentBatch.draw(layers[2], i * layers[2].getRegionWidth() - 1024, -160);
    }
    parentBatch.end();
  }

  /**
   * The ship's maximum speed; given in pixels per second.
   */
  private static final float MAX_SPEED        = 240;

  /**
   * The ship's maximum acceleration; given in pixels per second².
   */
  private static final float MAX_ACCELERATION = 10;

  /**
   * The ship's maximum deceleration; given in pixels per second².
   * <p>
   * This is the force that makes the ship to stop flying.
   */
  private static final float MAX_DECELERATION = MAX_ACCELERATION / 2;

  /**
   * The ship's position.
   */
  private final Vector2      position;

  /**
   * The ship's velocity.
   */
  private final Vector2      velocity;

  /**
   * The ship's acceleration.
   */
  private final Vector2      acceleration;

  private void calculCameraPosition(float delta)
  {
    if (Gdx.input.isPeripheralAvailable(Peripheral.Accelerometer))
    {

      // set the acceleration base on the accelerometer input; notice the
      // inverted axis because the game is displayed in landscape mode
      acceleration.set(Gdx.input.getAccelerometerY(), Gdx.input.getAccelerometerX());

      // set the acceleration bounds
      VectorUtils.adjustByRange(acceleration, -2, 2);

      // set the input deadzone
      if (!VectorUtils.adjustDeadzone(acceleration, 1f, 0f))
      {
        // we're out of the deadzone, so let's adjust the acceleration
        // (2 is 100% of the max acceleration)
        acceleration.x = (acceleration.x / 2 * MAX_ACCELERATION);
        acceleration.y = (-acceleration.y / 2 * MAX_ACCELERATION);
      }

    }
    else
    {
      // when the keys aren't pressed the acceleration will be zero, so
      // the ship's velocity won't be affected by it
      acceleration.x = (Gdx.input.isKeyPressed(Input.Keys.LEFT) ? -MAX_ACCELERATION : (Gdx.input
          .isKeyPressed(Input.Keys.RIGHT) ? MAX_ACCELERATION : 0));
      acceleration.y = (Gdx.input.isKeyPressed(Input.Keys.UP) ? MAX_ACCELERATION : (Gdx.input
          .isKeyPressed(Input.Keys.DOWN) ? -MAX_ACCELERATION : 0));
    }

    // if there is no acceleration and the ship is moving, let's calculate
    // an appropriate deceleration
    if (acceleration.len() == 0f && velocity.len() > 0f)
    {

      // horizontal deceleration
      if (velocity.x > 0)
      {
        acceleration.x = -MAX_DECELERATION;
        if (velocity.x - acceleration.x < 0)
        {
          acceleration.x = -(acceleration.x - velocity.x);
        }
      }
      else if (velocity.x < 0)
      {
        acceleration.x = MAX_DECELERATION;
        if (velocity.x + acceleration.x > 0)
        {
          acceleration.x = (acceleration.x - velocity.x);
        }
      }

      // vertical deceleration
      if (velocity.y > 0)
      {
        acceleration.y = -MAX_DECELERATION;
        if (velocity.y - acceleration.y < 0)
        {
          acceleration.y = -(acceleration.y - velocity.y);
        }
      }
      else if (velocity.y < 0)
      {
        acceleration.y = MAX_DECELERATION;
        if (velocity.y + acceleration.y > 0)
        {
          acceleration.y = (acceleration.y - velocity.y);
        }
      }

    }

    // modify and check the ship's velocity
    velocity.add(acceleration);
    VectorUtils.adjustByRange(velocity, -MAX_SPEED, MAX_SPEED);

    // modify and check the ship's position, applying the delta parameter
    position.add(velocity.x * delta, velocity.y * delta);

    if (VectorUtils.adjustByRangeY(position, -20, (getStage().getHeight() - getHeight()) * 2)) velocity.y = 0;
  }

  public void moveCamera(int x, int y, int pointer)
  {
    camera.unproject(curr.set(x, y, 0));
    if (!(last.x == -1 && last.y == -1 && last.z == -1))
    {
      camera.unproject(delta.set(last.x, last.y, 0));
      delta.sub(curr);
      camera.position.add(delta.x, delta.y, 0);
    }

    last.set(x, y, 0);
  }
}
